<?php

namespace Upfor\UpforPHP\Cache;

use Upfor\UpforPHP\Cache\AbstractCache;
use Upfor\UpforPHP\Interfaces\CacheInterface;

/**
 * File cache
 */
class File extends AbstractCache implements CacheInterface {

    const PERSISTENT = true;

    protected $options = [
        'ttl' => 0,
        'prefix' => '',
        'subDir' => false,
        'cachePath' => DATA_DIR . '/file_cache',
    ];

    protected $header = '<?php exit(); ?>';

    protected $headerLength;

    function __construct($options = array()) {
        if (!empty($options)) {
            $this->options = array_merge($this->options, $options);
            $this->options['prefix'] = str_replace('\\', '/', trim($this->options['prefix'], '/\\'));
        }

        rtrim($this->options['cachePath'], '/\\');

        $this->prefix = $this->options['prefix'];

        $this->checkDir();
        $this->headerLength = strlen($this->header);
    }

    /**
     * Store data in the cache
     *
     * @param  string  $key
     * @param  mixed   $value
     * @param  integer $ttl
     * @return boolean
     */
    public function set($key, $value, $ttl = null) {
        if (is_null($ttl) || $ttl < 0) {
            $ttl = $this->options['ttl'];
        }
        $data = array(
            'content' => $value,
            'ttl' => (int) $ttl,
            'dateline' => time(),
        );

        $orgFile = $this->getFilePath($key);
        $tmpFile = $orgFile . '.' . str_replace(' ', '.', microtime()) . '.' . mt_rand();
        if (@file_put_contents($tmpFile, $this->header . serialize($data), LOCK_EX)) {
            if (@rename($tmpFile, $orgFile)) {
                clearstatcache();

                return true;
            }
        }

        return false;
    }

    /**
     * Returns TRUE if the cache key exists and FALSE if not
     *
     * @param  string $key Cache key
     * @return boolean
     */
    public function has($key) {
        return $this->get($key) !== false;
    }

    /**
     * Fetch data from the cache
     *
     * @param  string $key
     * @return mixed|false
     */
    public function get($key) {
        $data = $this->getPayload($key);

        if ($data['ttl'] > 0 && ($data['dateline'] + $data['ttl']) < time()) {
            @unlink($this->getFilePath($key)); // Delete expired cache

            return false;
        }

        if (isset($data['content'])) {
            return $data['content'];
        }

        return false;
    }

    /**
     * Delete data from the cache
     *
     * @param  string $key
     * @return boolean
     */
    public function delete($key) {
        $file = $this->getFilePath($key);
        if (file_exists($file)) {
            return @unlink($file);
        }

        return true;
    }

    /**
     * Clears the cache
     *
     * @return boolean
     */
    public function clear() {
        $fileList = (array) glob($this->prefixedPath() . '*');
        foreach ($fileList as $path) {
            if (is_dir($path)) {
                array_map('unlink', glob($path . '/*.php'));
                rmdir($path);
            } else {
                unlink($path);
            }
        }

        return true;
    }

    /**
     * Increment
     *
     * @param  string $key
     * @param  mixed  $value
     * @return mixed
     */
    public function increment($key, $value = 1) {
        $data = $this->getPayload($key);
        if (isset($data['content'])) {
            $newData = (int) $data['content'] + $value;
            if ($this->set($key, $newData, $data['ttl'])) {
                return $newData;
            }
        }

        return false;
    }

    /**
     * Decrement
     *
     * @param  string $key
     * @param  mixed  $value
     * @return mixed
     */
    public function decrement($key, $value = 1) {
        return $this->increment($key, $value * -1);
    }

    /**
     * Is support persistent?
     *
     * @return boolean
     */
    public function persistent() {
        return self::PERSISTENT;
    }

    protected function getPayload($key) {
        $file = $this->getFilePath($key);
        if (file_exists($file)) {
            $data = unserialize(substr(@file_get_contents($file), $this->headerLength));
            if (!isset($data['dateline'])) {
                $data['dateline'] = @filemtime($file);
            }

            return $data;
        }

        return false;
    }

    /**
     * Check directory availability
     */
    protected function checkDir($dir = null) {
        if (empty($dir)) {
            $dir = $this->prefixedPath();
        }

        if (!is_dir($dir)) {
            mkdir($dir, 0775, true);
        }
    }

    protected function getFilePath($key) {
        $name = $this->createKey($key);
        if ($this->options['subDir']) {
            $name = substr($name, 0, 2) . '/' . substr($name, 2);
        }

        $filePath = $this->prefixedPath() . $name . '.php';
        $dirName = pathinfo($filePath, PATHINFO_DIRNAME);
        $this->checkDir($dirName);

        return $filePath;
    }

    /**
     * Get prefixed path of storage
     *
     * @return string
     */
    protected function prefixedPath() {
        return $this->options['cachePath'] . '/' . ($this->prefix ? $this->prefix . '/' : '');
    }

}
